package checkcomponentlogic

import (
	"context"
	"errors"
	"fmt"
	"github.com/zeromicro/go-zero/core/stores/sqlx"
	"os"
	"os/exec"
	"path/filepath"
	"strconv"
	"sync"
	"yunzhan/common/models"
	"yunzhan/common/utils"

	"yunzhan/rpc-server/internal/svc"
	agent "yunzhan/rpc-server/pb"

	"github.com/zeromicro/go-zero/core/logx"
)

type CheckComponentStatusLogic struct {
	ctx    context.Context
	svcCtx *svc.ServiceContext
	logx.Logger
}

func NewCheckComponentStatusLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CheckComponentStatusLogic {
	return &CheckComponentStatusLogic{
		ctx:    ctx,
		svcCtx: svcCtx,
		Logger: logx.WithContext(ctx),
	}
}

func (l *CheckComponentStatusLogic) CheckComponentStatus(in *agent.CheckComponentRequest) (*agent.CheckComponentResponse, error) {
	tableName := l.svcCtx.Config.SQLite.Tables.ComponentInfo
	if l.svcCtx.Config.UseMysql {
		tableName = l.svcCtx.Config.Mysql.Tables.ComponentInfo
	}

	exePath, err := os.Executable()
	if err != nil {
		return nil, fmt.Errorf("error getting executable path: %w", err)
	}

	parentDir := filepath.Dir(filepath.Dir(exePath))
	tmpDir := filepath.Join(parentDir, "tmp")
	clusterFilePath := filepath.Join(tmpDir, "CLUSTER")

	clusterID, err := models.ReadClusterID(clusterFilePath)
	if err != nil {
		return nil, fmt.Errorf("failed to read cluster ID: %w", err)
	}

	querySQL := fmt.Sprintf(`SELECT cluster_id, component_name, version, node_host, node_ip, component_role, port, status FROM %s WHERE cluster_id = ? AND node_ip = ?`, tableName)
	var componentInfos []*agent.ComponentStatus
	err = l.svcCtx.Conn.QueryRows(&componentInfos, querySQL, clusterID, in.ComponentStatus.NodeIP)
	if err != nil {
		return nil, fmt.Errorf("failed to query component info: %w", err)
	}

	if len(componentInfos) == 0 {
		return &agent.CheckComponentResponse{
			Code:    200,
			Message: "no component information found",
		}, nil
	}

	componentResponse := make([]*agent.ComponentStatus, 0, len(componentInfos))
	errChan := make(chan error, len(componentInfos))
	var wg sync.WaitGroup
	wg.Add(len(componentInfos))

	for _, componentInfo := range componentInfos {
		go func(info *agent.ComponentStatus) {
			defer wg.Done()
			status, err := l.checkProcessStatus(info.ComponentName, info.Port)
			if err != nil {
				errChan <- fmt.Errorf("failed to check process status for %s: %w", info.ComponentName, err)
				return
			}

			info.Status = status
			var mutex sync.Mutex
			mutex.Lock()
			componentResponse = append(componentResponse, info)
			mutex.Unlock()
		}(componentInfo)
	}

	wg.Wait()
	close(errChan)

	for err = range errChan {
		if err != nil {
			return nil, err
		}
	}

	err = l.svcCtx.Conn.Transact(func(session sqlx.Session) error {
		for _, info := range componentResponse {
			updateSQL := fmt.Sprintf(`
                INSERT INTO %s (cluster_id, component_name, version, node_host, component_role, port, status) 
                VALUES (?, ?, ?, ?, ?, ?, ?)
                ON DUPLICATE KEY UPDATE status = VALUES(status)`, tableName)

			_, err := session.Exec(updateSQL, info.ClusterID, info.ComponentName, info.Version, info.NodeHost, info.ComponentRole, info.Port, info.Status)
			if err != nil {
				return fmt.Errorf("failed to update component status: %w", err)
			}
		}
		return nil
	})

	if err != nil {
		return nil, err
	}

	return &agent.CheckComponentResponse{
		Code:    200,
		Message: "component status updated successfully",
	}, nil
}

func (l *CheckComponentStatusLogic) checkProcessStatus(componentName, port string) (bool, error) {
	if port == "" {
		// 如果没有端口信息，则仅使用进程名检查
		checkCommand := fmt.Sprintf(`sudo pgrep -f %s`, componentName)
		output, err := utils.ExecCommand(checkCommand)
		if err != nil {
			return false, err
		}
		return output != "", nil
	}

	portNum, err := strconv.Atoi(port)
	if err != nil {
		return false, fmt.Errorf("invalid port number: %s", port)
	}

	// 使用 netstat 命令检查进程名和端口号
	//checkCommand := fmt.Sprintf(`sudo netstat -nlp | awk '$4 ~ /:%d$/ && $NF ~ "%s"'`, portNum, componentName)
	checkCommand := fmt.Sprintf(`sudo netstat -nlp | grep %s | grep :%d`, componentName, portNum)
	output, err := utils.ExecCommand(checkCommand)
	if err != nil {
		var exitError *exec.ExitError
		if errors.As(err, &exitError) {
			// netstat 命令返回非零退出码，表示未找到匹配的进程
			return false, nil
		}
		return false, fmt.Errorf("exec command failed: %w", err)
	}

	return len(output) > 0, nil
}
